//
//    node-l3qd - The L3q daemon that runs on each compute node.
//    Copyright (C) 2022-2023  Marcus Pedersén marcus@marcux.org
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

/// Accepts POST requests on url:
/// https://<node-l3qd>/l3qd/request
/// The correct structs are sent as
/// serialized json data.
/// If wrong json data is sent an
/// empty json is returned.
/// The status node sends task status
/// to l3qd the central daemon as a
/// POST to:
/// https://<l3qd>:<port>/node-l3qd
/// with serialized json data.


use actix::prelude::*;
use actix::sync::SyncContext;
use actix_web::{
    post, web, App,
    middleware, HttpResponse, HttpServer
};
use rcgen::generate_simple_self_signed;
use gethostname::gethostname;
use openssl::{
    pkey::PKey,
    ssl::{SslAcceptor, SslMethod},
    x509::X509,
};

mod comm;
use comm::node::*;
use comm::all::*;

mod db;
use db::{
    Pool,
    DbQuery,
    DbResult,
    execute,
    execute_status,
};

use serde_json::json;
use serde::Deserialize;
use sysinfo::{System, SystemExt};
use passwords::PasswordGenerator;
use dns_lookup::lookup_addr;
use reqwest::blocking::Client;
use std::process::Command;
use std::{fs, io};
use std::path::Path;
use std::io::Write;
use std::{thread, time};

const SYSTEMD_UNIT_NAME: &str = "node-l3qd-task-";

/// Config and default values
const CONFIG_FILE: &str = "/etc/l3q/node-l3qd.conf";
const PORT: u16 = 39911;
const L3QD_PORT: u16 = 39911;
const DB_FILE: &str = "/var/lib/l3q/node-l3qd.db";
const L3QD_LOG: &str = "/var/log/l3q/node-l3qd.log";
const SYSTEMD_SLICE_NAME: &str = "l3q.slice";
/// Min value is 10 secs
const CHECK_STATUS_INTERVAL: u64 = 60;
const TASK_OUTPUT_DIRECTORY: &str = "/var/lib/l3q/task-output/";

/// Contains configuration
/// read from node-l3qd.conf file.
#[derive(Debug, Deserialize)]
struct FileConfig {
    port: Option<u16>,
    l3qd_port: Option<u16>,
    db_file: Option<String>,
    l3qd_log: Option<String>,
    systemd_slice_name: Option<String>,
    check_status_interval: Option<u64>,
    task_output_directory: Option<String>,
}


/// Config used internally
#[derive(Debug, Clone)]
struct Config {
    port: u16,
    l3qd_port: u16,
    db_file: String,
    l3qd_log: String,
    systemd_slice_name: String,
    check_status_interval: u64,
    task_output_directory: String,
}


/// Message that is sent
/// to actor when it should
/// start.
#[derive(Message)]
#[rtype(result="()")]
struct TaskStatusActorStart {
    conf: Config,
    validate: String,
    l3qd_host: String,
}

/// Actor running in an own
/// thread checking task
/// status at regular interval.
struct TaskStatusActor;

impl Actor for TaskStatusActor {
    type Context = SyncContext<Self>;
}

impl Handler<TaskStatusActorStart> for TaskStatusActor {
    type Result = ();

    /// When message is sent to actor
    /// the collect status loop will start.
    fn handle(&mut self, msg: TaskStatusActorStart, _: &mut Self::Context) {
        log::info!("Starting task status collect thread");
        collect_task_info(msg.conf, msg.validate, msg.l3qd_host);
    }
}

/// Returns Config read from
/// node-l3qd.conf, with default
/// values populated if values
/// are missing in file.
fn get_config() -> Result<Config, (Config, String)> {
    let mut conf = Config {
	port: PORT,
        l3qd_port: L3QD_PORT,
	db_file: DB_FILE.to_string(),
	l3qd_log: L3QD_LOG.to_string(),
        systemd_slice_name: SYSTEMD_SLICE_NAME.to_string(),
        check_status_interval: CHECK_STATUS_INTERVAL,
        task_output_directory: TASK_OUTPUT_DIRECTORY.to_string(),
    };


    match std::fs::read_to_string(CONFIG_FILE) {
	Ok(cont) => {
	    match toml::from_str::<FileConfig>(&cont) {
		Ok(c) => {
		    if c.port.is_some() {
			conf.port = c.port.unwrap();
		    }

                    if c.l3qd_port.is_some() {
                        conf.l3qd_port = c.l3qd_port.unwrap();
                    }
		    
		    if c.db_file.is_some() {
			conf.db_file = c.db_file.unwrap();
		    }

		    if c.l3qd_log.is_some() {
			conf.l3qd_log = c.l3qd_log.unwrap();
		    }

                    if c.systemd_slice_name.is_some() {
                        conf.systemd_slice_name = c.systemd_slice_name.unwrap();
                    }

                    if c.check_status_interval.is_some() {
                        let i = c.check_status_interval.unwrap();

                        if i < 10 {
                            conf.check_status_interval = 10;
                        }
                        else {
                            conf.check_status_interval = i;
                        }
                    }

                    if c.task_output_directory.is_some() {
                        let task_out = c.task_output_directory.unwrap();

                        if task_out.ends_with("/") {
                            conf.task_output_directory = task_out;
                        }
                        else {
                            conf.task_output_directory = format!("{task_out}/");
                        }
                    }

		    return Ok(conf);
		}
		Err(e) => {
		    return Err((conf,
				format!("Failed to parse config file: {}\n{}\nCheck configuration",
					CONFIG_FILE, e)));
		}
	    }
	},
	Err(e) => {
	    return Err((conf,
			format!("Failed to read config file: {}\n{}\nCheck configuration",
				CONFIG_FILE, e)));
	}		
    }
}




/// Goes though all used
/// CPUs in sub-groups of
/// l3q cgroup and
/// returns all CPUs that
/// are not allocated.
fn get_available_cpus(sysd_slice: &String) -> io::Result<Vec<usize>> {
    let str_path = format!("/sys/fs/cgroup/{}", sysd_slice);
    let cgroup_path = Path::new(&str_path);
    let mut avail_cpus: Vec<usize> = Vec::new();

    for c in 0..num_cpus::get() {
        avail_cpus.push(c);
    }

    if cgroup_path.is_dir() {
        let mut used_cpus: Vec<usize> = Vec::new();

        for entry in fs::read_dir(cgroup_path)? {
            let entry = entry?;
            let path = entry.path();

            if path.is_dir() && path.to_string_lossy().contains(SYSTEMD_UNIT_NAME) {
                let path = format!("{}/{}", path.to_str().unwrap(), "cpuset.cpus");
                let cpus = fs::read_to_string(path.clone())?;

                for c in cpus.split(",") {
                    if c.contains("-") {
                        let cpu_split: Vec<&str> = c.split("-").collect();
                        
                        if cpu_split.len() == 2 {
                            let low_cpu: usize;
                            let high_cpu: usize;
                            
                            match cpu_split[0].trim().parse::<usize>() {
                                Ok(c) => low_cpu = c,
                                Err(e) => {
                                    log::error!("Failed to parse CPU number from file: {}, CPUs: {}, {}", path, cpu_split[0], e);
                                    continue;
                                }
                            }
                            
                            match cpu_split[1].trim().parse::<usize>() {
                                Ok(c) => high_cpu = c,
                                Err(e) => {
                                    log::error!("Failed to parse CPU number from file: {}, CPUs: {}, {}", path, cpu_split[1], e);
                                    continue;
                                }
                            }
                            
                            for n in low_cpu..=high_cpu {
                                used_cpus.push(n);
                            }
                        }
                    }
                    else {
                        match c.trim().parse::<usize>() {
                            Ok(c) => used_cpus.push(c),
                            Err(e) =>  log::error!("Failed to parse CPU number from file: {}, CPUs: {}, {}", path, c, e),
                        }
                    }
                }
            }
        }

        used_cpus.sort();
        used_cpus.dedup();

        avail_cpus.retain(|&c| ! used_cpus.contains(&c));
    }

    Ok(avail_cpus)
}


/// Returns the speciefied number of CPUs
/// that is not allocated to another task.
/// If not specified number of CPUs are available
/// the rest of available CPUs are returned.
/// If argument is zero all available CPUs are returned.
/// Atleast one core is returned.
fn request_cpus(no_cores: u64, sysd_slice: &String) -> Result<String, String> {
    let all_cpus: u64 = num_cpus::get() as u64;

    let avail_cores;
    match get_available_cpus(sysd_slice) {
        Ok(c) => avail_cores = c,
        Err(e) => {
            eprintln!("{e}");
            return Err("Failed to get available cpus".to_string());
        },
    }
    let mut alloc_cores =  String::from("");

    if avail_cores.len() == 0 {
        return Err("No available cores".to_string());
    }
    else if no_cores == 0 {
        for c in avail_cores {
            if alloc_cores.len() == 0 {
                alloc_cores = format!("{}", c);
            }
            else {
                alloc_cores = format!("{},{}", alloc_cores, c);
            }
        }
    }
    else if  no_cores > all_cpus || no_cores > avail_cores.len() as u64 {
        return Err("Not enough cores are available".to_string());
    }
    else {
        for (i, c) in avail_cores.iter().enumerate() {
            if alloc_cores.len() == 0 {
                alloc_cores = format!("{}", c);
            }
            else if (i as u64) < no_cores - 1 {
                alloc_cores = format!("{},{}", alloc_cores, c);
            }
            else {
                break;
            }
        }
    }

    Ok(alloc_cores)
}

/// Starts execution of specified task
/// on this node as specified user.
/// Returns which CPUs that are
/// allocated to this job
fn execute_task(job: &mut NodeL3qdJob, task_idx: usize, conf: &Config) -> Result<(), String> {
    let memory_max: String;
    let memory_high: String;
    let cpus: String;

    if task_idx >= job.tasks.len() {
        return Err(format!("fn execute_task: Task index: {} is outside of task list: {} in jobid: {}",
                           task_idx, job.tasks.len(), job.jobid));
    }
    
    if job.memory_alloc == 0 {
        memory_max = String::from("infinity");
        memory_high = String::from("infinity");
    }
    else {
        memory_max = format!("{}", job.memory_alloc as f64 * 0.05);
        memory_high = format!("{}", job.memory_alloc);
    }

    match &job.cgroup_cores_alloc {
        Some(c) => cpus = c.to_string(),
        None    => {
            match request_cpus(job.cores_alloc, &conf.systemd_slice_name) {
                Ok(c) => {
                    cpus = c;
                    job.cgroup_cores_alloc = Some(cpus.clone());
                },
                Err(e) => return Err(format!("Failed to request cpus, {}", e)),
            }
        },
    }
    
    
    let task_out_res = Command::new("/usr/bin/systemd-run")
        .arg(format!("--property=AllowedCPUs={}", cpus))
        .arg(format!("--property=MemoryHigh={}", memory_high))
        .arg(format!("--property=MemoryMax={}", memory_max))
        //systemd version >= 240
        .arg(format!("--property=StandardOutput=append:{}{}{}-{}.stdout",
                     conf.task_output_directory, SYSTEMD_UNIT_NAME, job.jobid, job.tasks[task_idx].taskid))
        .arg(format!("--property=StandardError=append:{}{}{}-{}.stderr",
                     conf.task_output_directory, SYSTEMD_UNIT_NAME, job.jobid, job.tasks[task_idx].taskid))
        .arg(format!("--uid={}", job.user))
        .arg(format!("--gid={}", job.group))
        .arg("--no-ask-password")
        .arg("--no-block")
        .arg("--remain-after-exit")
        .arg("--service-type=oneshot")
        .arg(format!("--slice={}", conf.systemd_slice_name))
        .arg(format!("--unit={}{}-{}", SYSTEMD_UNIT_NAME, job.jobid, job.tasks[task_idx].taskid))
        .arg(format!("--working-directory={}", job.tasks[task_idx].working_directory))
        .arg(job.tasks[task_idx].command.clone())
        .output();


    let task_out;
    match task_out_res {
        Ok(t) => task_out = t,
        Err(e) => return Err(format!("Failed to execute job: {}, task: {}, {}",
                                     job.jobid, job.tasks[task_idx].taskid, e)),
    }
        
    if task_out.status.success() {
        Ok(())
    }
    else {
        Err(format!("Failed to start job: {}, task: {}, {}", job.jobid, job.tasks[task_idx].taskid,
              String::from_utf8_lossy(&task_out.stderr)))
    }
}

/// Return current status
/// and info on specified
/// task.
fn status_task(conf: &Config, task_unit_name: String, get_stdout: bool) -> Result<TaskStatus, String> {
    let mut status = TaskStatus::new();
    let name_split: Vec::<&str> = task_unit_name.split("-").collect();
    if name_split.len() == 5 {
        match name_split[3].parse::<u64>() {
            Ok(j) => status.jobid = j,
            Err(e) => return Err(format!("Failed to parse jobid, {e}")),
        }

        let taskid_split: Vec<&str> = name_split[4].split(".").collect();

        if taskid_split.len() == 2 {
            match taskid_split[0].parse::<u64>() {
                Ok(t) => status.taskid = t,
                Err(e) => return Err(format!("Failed to parse jobid, {e}")),
            }
        }
        else {
            return Err("Failed to parse taskid".to_string());
        }
    }
    else {
        return Err("Failed to parse jobid and taskid".to_string());
    }

    status.unitname = task_unit_name.clone();
    
    let task_out_res = Command::new("/usr/bin/systemctl")
        .arg("show")
        .arg(&task_unit_name)
        .output();

    let task_out;
    match task_out_res {
        Ok(t) => task_out = t,
        Err(e) => return Err(format!("Failed to run systemctl show for task: {}, {}",
                                     task_unit_name, e)),
    }

    if ! task_out.status.success() {
        return Err(format!("Failed to run systemctl show for task: {}, {}",
                           &task_unit_name, String::from_utf8_lossy(&task_out.stderr)));
    }

    let task_stdout = String::from_utf8_lossy(&task_out.stdout);

    for line in task_stdout.split('\n')  {
        if line.starts_with("MainPID=") {
            let val:Vec<&str> = line.split("=").collect();
            if val.len() >= 2 {
                match val[1].trim().parse::<u32>() {
                    Ok(v) => {
                        if v > 0 {
                            status.main_pid = v;
                        }
                    },
                    Err(_) => (),
                }
            }
        }

        if line.starts_with("ExecMainStartTimestamp=") {
            let val:Vec<&str> = line.split("=").collect();        
            if val.len() >= 2 {
                let mut raw_time = val[1].trim().to_string();

                if raw_time.len() == 27 {
                    match raw_time.split_once(' ') {
                        Some(s) => {
                            raw_time = s.1.to_string();
                            match raw_time.rsplit_once(' ') {
                                Some(s) => raw_time = s.0.to_string(),
                                None => (),
                            }
                        },
                        None => (),
                    }
                }
                status.start_time = raw_time;
            }
        }

        if line.starts_with("ExecMainStartTimestampMonotonic=") {
            let val:Vec<&str> = line.split("=").collect();        
            if val.len() >= 2 {
                match val[1].trim().parse::<u64>() {
                    Ok(v) => status.mono_start_time = v,
                    Err(_) => (),
                }
            }
        }
        
        if line.starts_with("ExecMainExitTimestamp=") {
            let val:Vec<&str> = line.split("=").collect();        
            if val.len() >= 2 {
                let mut raw_time = val[1].trim().to_string();

                if raw_time.len() == 27 {
                    match raw_time.split_once(' ') {
                        Some(s) => {
                            raw_time = s.1.to_string();
                            match raw_time.rsplit_once(' ') {
                                Some(s) => raw_time = s.0.to_string(),
                                None => (),
                            }
                        },
                        None => (),
                    }
                }
                status.end_time = raw_time;
            }
        }
        
        if line.starts_with("ExecMainExitTimestampMonotonic=") {
            let val:Vec<&str> = line.split("=").collect();        
            if val.len() >= 2 {
                match val[1].trim().parse::<u64>() {
                    Ok(v) => status.mono_end_time = v,
                    Err(_) => (),
                }
            }
        }

        if line.starts_with("ActiveState=") {
            let val:Vec<&str> = line.split("=").collect();        
            if val.len() >= 2 {
                status.active_state = val[1].trim().to_string();
            }
        }

        if line.starts_with("SubState=") {
            let val:Vec<&str> = line.split("=").collect();        
            if val.len() >= 2 {
                status.sub_state = val[1].trim().to_string();
            }
        }

        if line.starts_with("ExecMainStatus=") {
            let val:Vec<&str> = line.split("=").collect();        
            if val.len() >= 2 {
                match val[1].trim().parse::<u16>() {
                    Ok(v) => status.exit_code = v,
                    Err(_) => (),
                }
            }
        }
    }


    
    match fs::read_to_string(
        format!("/sys/fs/cgroup/{}/{}/memory.peak", conf.systemd_slice_name, task_unit_name)) {
        Ok(s) => {
            match s.trim().parse::<u64>() {
                Ok(u) => {
                    if u > 0 {
                        status.memory_peak = u;
                    }
                },
                Err(e) => return Err(
                    format!("Failed to parse memory peak:{e}, from file: /sys/fs/cgroup/{}/{}/memory.peak", conf.systemd_slice_name, task_unit_name)),
            }
        },
        Err(_) => (),
    }
    
    match fs::read_to_string(
        format!("/sys/fs/cgroup/{}/{}/pids.peak", conf.systemd_slice_name, task_unit_name)) {
        Ok(s) => {
            match s.trim().parse::<u32>() {
                Ok(u) => {
                    if u > 0 {
                        status.pids_peak = u;
                    }
                },
                Err(e) => return Err(
                    format!("Failed to parse pids peak:{e}, from file: /sys/fs/cgroup/{}/{}/pids.peak", conf.systemd_slice_name, task_unit_name)),
            }
        }
        Err(_) => (),
    }

    if get_stdout {
        match fs::read_to_string(
            format!("{}{}{}-{}.stdout",
                    conf.task_output_directory,
                    SYSTEMD_UNIT_NAME,
                    status.jobid,
                    status.taskid)) {
            Ok(o) => status.stdout = o,
            Err(e) => 
                log::error!("STATUS THREAD: Failed to parse stdout for jobid:{}, taskid: {}, {}",
                            status.jobid, status.taskid, e),
        }

        match fs::read_to_string(
            format!("{}{}{}-{}.stderr",
                    conf.task_output_directory,
                    SYSTEMD_UNIT_NAME,
                    status.jobid,
                    status.taskid)) {
            Ok(o) => status.stderr = o,
            Err(e) =>
                log::error!("STATUS THREAD: Failed to parse stderr for jobid:{}, taskid: {}, {}",
                            status.jobid, status.taskid, e),
        }
    }

    Ok(status)
}


/// Prints information on
/// tasks executing on this node
fn print_tasks(conf: &Config) {
    match status_all_tasks(conf, false) {
        Ok(tasks) => {
            if tasks.len() == 0 {
                println!("No tasks are running in L3q on this node.");
            }
            else {
                println!("{:-<141}", "");
                println!("|{:^8}|{:^8}|{:^30}|{:^30}|{:^6}|{:^11}|{:^40}|",
                         "Jobid", "Taskid", "Start time", "End time", "Exit", "State", "Systemd");
                println!("{:-<141}", "");
                
                for t in tasks {
                    let state;
                    let end_time;
                    let exit_code;
                    
                    if t.sub_state == "start" {
                        state = "Executing".to_string();
                        end_time = "-".to_string();
                        exit_code = "-".to_string();
                    }
                    else {
                        state = t.sub_state;
                        end_time = t.end_time;
                        exit_code = format!("{}", t.exit_code);
                    }

                    println!("|{:^8}|{:^8}|{:^30}|{:^30}|{:^6}|{:^11}|{:^40}|",
                             t.jobid, t.taskid, t.start_time, end_time, exit_code, state, t.unitname);
                    println!("{:-<141}", "");
                }
            }
        },
        Err(e) => {
            eprintln!("Failed to get tasks from node-l3qd.");
            eprintln!("Error: {e}");
            std::process::exit(3);
        },
    }
}


/// Prints help text and exit
fn print_help() {
    println!("Usage: {} [OPTION]...",
             env!("CARGO_PKG_NAME"));
    println!("The L3q daemon that runs on each compute node.");
    println!("node-l3qd communicates with central L3q daemon (l3qd)");
    println!("and execute tasks on this compute node.");
    println!("");
    println!("Options:");
    println!("    --status         Print information for tasks on this node.");
    println!("                     Same information will be printed if called");
    println!("                     without arguments.");
    println!("    --daemon         Will start in daemon mode, start processing");
    println!("                     tasks and communicating with central L3q");
    println!("                     daemon (l3qd). This is the normal operation");
    println!("                     mode. If daemon is not running status on");
    println!("                     running tasks will not be updated.");
    println!("                     If daemon is not running new tasks can not");
    println!("                     be received from l3qd and new tasks will not");
    println!("                     be launched. Central l3q daemon (l3qd) will");
    println!("                     show this node as offline as it can not be reached.");
    println!("    --validate       Without validation the node daemon on this host will");
    println!("                     not be able to communicate with L3q daemon.");
    println!("                     Start the validation server on the same server");
    println!("                     as the L3q daemon with:");
    println!("                     l3qd --validate");
    println!("                     and follow instructions.");
    println!("                     root previlegies are required to run this command.");
    println!("    --version, -V    Prints version and exit.");
    println!("    --help, -h       Prints this help text and exit.");
    println!("");
    println!("node-l3qd only takes one argument at a time,");
    println!("multiple arguments can not be specified.");
    println!("");
    println!("Documentation available locally via: man node-l3qd");
    println!("Documentation for the entire system can be found at:");
    println!("<https://notabug.org/marcux/l3q/wiki>");
    println!("");
    println!("Report bugs to: <https://notabug.org/marcux/l3q/issues>");
}


/// Is called if wrong arguments
/// are specified.
/// Only single (one) argument
/// is valid.
fn print_wrong_args() {
    println!("Wrong arguments. Try -h or --help for help.");
    std::process::exit(2);
}



/// Collect status for all
/// running tasks in node-l3qd
fn status_all_tasks(conf: &Config, get_stdout: bool) -> Result<Vec<TaskStatus>, String> {
    let mut tasks: Vec<TaskStatus> = Vec::new();

    let units_out_res = Command::new("/usr/bin/systemctl")
        .arg("show")
        .arg(&conf.systemd_slice_name)
        .output();

    let units_out;
    match units_out_res {
        Ok(t) => units_out = t,
        Err(e) => return Err(format!("Failed to run systemctl show for slice: {}, {}",
                                     conf.systemd_slice_name, e)),
    }

    if ! units_out.status.success() {
        return Err(format!("Failed to run systemctl show for slice: {}, {}",
                           conf.systemd_slice_name, String::from_utf8_lossy(&units_out.stderr)));
    }

    let units_stdout = String::from_utf8_lossy(&units_out.stdout);

    for line in units_stdout.split('\n')  {
        if line.starts_with("RequiredBy=") {
            let require_split: Vec<&str> = line.split("RequiredBy=").collect();

            for u in require_split[1].split(" ") {
                match status_task(conf, u.to_string(), get_stdout) {
                    Ok(t) => tasks.push(t),
                    Err(e) => return Err(format!("Failed to get status for task: {}, {}", u, e)),
                }
            }
        }
    }
    
    Ok(tasks)
}


/// Function removes stdout
/// and stderr files for
/// specified task.
/// To be run at clean up
/// after task has terminated.
fn clean_task_ouput_files(config: &Config, unitname: &String) -> std::io::Result<()> {
    let task = unitname.trim_end_matches(".service");
    let err_path = Path::new(&config.task_output_directory).join(format!("{task}.stderr"));
    let out_path = Path::new(&config.task_output_directory).join(format!("{task}.stdout"));

    println!("{:?}",err_path);
    println!("{:?}",out_path);
    
    fs::remove_file(err_path)?;
    fs::remove_file(out_path)?;
    Ok(())
}


/// Runs in a separate thread
/// and collects data on running
/// tasks at even intervals
/// specified in config file
fn collect_task_info(conf: Config, validate: String, l3qd_host: String) {
    let db;
    match db::init_db(conf.db_file.clone()) {
        Ok(c) => db = c,
        Err(e) => {
            log::error!("STATUS THREAD: Failed to initialize database at path: {}\n{}",
                        conf.db_file, e);
            log::error!("STATUS THREAD: TERMINATING thread that collects info on tasks and status.");
            std::process::exit(1);
        },
    }

    loop {
	let mut db_tasks: Vec<TaskStatus> = Vec::new();
        match execute_status(&db, DbQuery::GetAllTasks) {
            Ok(t) => {
                db_tasks = t.into_iter().map(|x| DbResult::to_task_status(&x)).collect();
            },
            Err(e) => log::error!("STATUS THREAD: Failed to get all tasks from database: {e}"),
        }

        match status_all_tasks(&conf, true) {
            Ok(tasks) => {
		let cpus = num_cpus::get();
		let avail_cpus = match get_available_cpus(&conf.systemd_slice_name) {
		    Ok(c) => c.len() as i32,
		    Err(e) => {
			log::error!("STATUS THREAD: Failed to get available CPUs, {e}");
			-1
		    },
		};

		let mut used_cpus = -1;

		if avail_cpus >= 0 {
		    used_cpus = cpus as i32 - avail_cpus;
		}
    
		let mut sys = System::new_all();
		sys.refresh_all();

		let status_data = L3qdNodeData::NodeStatus {
		    total_cpus: cpus,
		    used_cpus: used_cpus,
		    total_memory: sys.total_memory(),
		    used_memory: sys.used_memory(),
		    total_swap: sys.total_swap(),
		    used_swap: sys.used_swap(),
		};

		let mut all_tasks: Vec<TaskStatus> = Vec::new();
                
		for mut t in tasks {
                    for (i, dt) in db_tasks.iter().enumerate() {
                        if t.jobid == dt.jobid || t.taskid == dt.taskid {
                            db_tasks.swap_remove(i);
                            break;
                        }
                    }
                    
		    if t.sub_state == "failed" {
			let res_failed_res = Command::new("/usr/bin/systemctl")
			    .arg("reset-failed")
			    .arg(t.unitname.clone())
			    .output();

			match res_failed_res {
			    Ok(_) => {
                                match clean_task_ouput_files(&conf, &t.unitname) {
                                    Ok(_) => (),
                                    Err(e) =>
                                        log::error!("STATUS THREAD: Failed to delete stdout and std error files for task unitname: {}, in directory: {}, {}",
                                                    t.unitname, conf.task_output_directory, e),
                                }
                            },
			    Err(e) => {
				t.active_state = String::from("Node-Err");
				log::error!("STATUS THREAD: Failed to reset-failed systemd service: {}, systemctl reset-failed failed: {}", t.unitname, e)
			    }
			}
                    }
                    else if t.sub_state == "exited" {
			let res_exit_res = Command::new("/usr/bin/systemctl")
			    .arg("stop")
			    .arg(t.unitname.clone())
			    .output();

			match res_exit_res {
			    Ok(_) => {
                                match clean_task_ouput_files(&conf, &t.unitname) {
                                    Ok(_) => (),
                                    Err(e) =>
                                        log::error!("STATUS THREAD: Failed to delete stdout and std error files for task unitname: {}, in directory: {}, {}",
                                                    t.unitname, conf.task_output_directory, e),
                                }
                            },
			    Err(e) => {
				t.active_state = String::from("Node-Err");
				log::error!("STATUS THREAD: Failed to stop systemd service: {}, systemctl stop failed: {}", t.unitname, e)
			    }
			}
                    }

                    all_tasks.push(t);
	        }

                all_tasks.append(&mut db_tasks);
                let all_tasks_db = all_tasks.clone();

                let l3qd_response = L3qdNodeResponse {
                    data: L3qdNodeData::StatusTasks {
                        tasks: all_tasks,
                        node_status: Box::new(status_data),
                    },
                    validate: validate.clone(),
                };

                let url = format!("https://{}:{}/node-l3qd", l3qd_host, conf.l3qd_port);
                let client = Client::builder()
                    .danger_accept_invalid_certs(true)
                    .build()
                    .unwrap();
                
                if l3qd_host.len() > 0 || validate.len() > 0 {
                    match client.post(url).json(&l3qd_response).send() {
                        Ok(r) => {
                            match r.json::<L3qdResponse>() {
                                Ok(resp) => {
                                    if resp.result.is_ok() {
                                        match execute_status(&db, DbQuery::DeleteAllTasks) {
                                            Ok(_) =>
                                                log::info!("STATUS THREAD: l3qd is online again and task status has been sent to central deamon"),
                                            Err(e) =>
                                                log::error!("STATUS THREAD: Failed to delete all tasks from database: {e}"),
                                        }
                                    }
                                    else {
                                        log::error!("STATUS THREAD: L3qd host failed to process tasks: {:?}", resp.result);
                                        for t in all_tasks_db {
                                            match execute_status(&db, DbQuery::to_insert_task(&t)) {
                                                Ok(_) => (),
                                                Err(e) => log::error!("STATUS THREAD: Failed to save task to database: {:?}\n{e}", t),
                                            }
                                        }
                                    }
                                },
                                Err(e) => {
                                    log::error!("STATUS THREAD: Failed to parse response from l3qd daemon: {}", e);
                                    for t in all_tasks_db {
                                        match execute_status(&db, DbQuery::to_insert_task(&t)) {
                                            Ok(_) => (),
                                            Err(e) => log::error!("STATUS THREAD: Failed to save task to database: {:?}\n{e}", t),
                                        }
                                    }
                                }
                            }
                        },
                        Err(e) => {
                            log::error!("STATUS THREAD: Failed to send tasks to l3qd host: {}, on port: {}, {}",
                                        l3qd_host, conf.l3qd_port, e);
                            for t in all_tasks_db {
                                match execute_status(&db, DbQuery::to_insert_task(&t)) {
                                    Ok(_) => (),
                                    Err(e) => log::error!("STATUS THREAD: Failed to save task to database: {:?}\n{e}", t),
                                }
                            }
                        },
                    }
                }
                else {
                    log::info!("STATUS THREAD: This L3Q node daemon has not been validated.");
                }
            },
            Err(e) =>
                log::error!("STATUS THREAD: Failed to get status for all running tasks: {e}"),
        }

        thread::sleep(time::Duration::from_secs(conf.check_status_interval));
    }
}


/// Called when --validate flag
/// is used.
/// Validates towards the L3q daemon
/// and saves the validation to database.
fn validate() {
    let mut l3q_daemon = String::new();
    let mut l3q_port_str = String::new();
    let l3q_port;

    match validate_print_help() {
        Ok(_) => (),
        Err(e) => {
            eprintln!("{e}");
            std::process::exit(6);
        },
    }

    let mut hostname = gethostname().to_string_lossy().to_string();
    let mut in_hostname = String::new();
    println!("Specify hostname of this host, this name will be the");
    println!("displayed name in l3qd when hosts are printed.");
    print!("Hostname (default {hostname}): ");
    io::stdout().flush().unwrap();
    match io::stdin().read_line(&mut in_hostname) {
        Ok(_) => {
            in_hostname = in_hostname.trim().to_string();
            if in_hostname.len() != 0 {
                hostname = in_hostname;
            }
        },
        Err(e) => {
            eprintln!("Failed to parse stdin.\n{}", e);
            std::process::exit(7);
        }
    }

    println!("");
    
    println!("Specify hostname or IP address to the L3q daemon.");

    loop {
        println!("");
        print!("L3q daemon hostname/IP address: ");
        io::stdout().flush().unwrap();
        match io::stdin().read_line(&mut l3q_daemon) {
            Ok(_) => {
                l3q_daemon = l3q_daemon.trim().to_string();
                if l3q_daemon.len() == 0 {
                    eprintln!("L3q daemon name can not be empty.");
                }
                else {
                    break;
                }
            },
            Err(e) => {
                eprintln!("Failed to parse stdin.\n{}", e);
            }
        }

        l3q_daemon = String::new();
        eprintln!("Please try again!");
    }

    println!("");
    println!("Specify port that the L3q validate daemon runs on.");
    println!("It is printed when the validate daemon is started.");

    loop {
        println!("");
        print!("L3q validate daemon port: ");
        io::stdout().flush().unwrap();
        match io::stdin().read_line(&mut l3q_port_str) {
            Ok(_) => {
                l3q_port_str = l3q_port_str.trim().to_string();
                if l3q_port_str.len() == 0 {
                    eprintln!("L3q validate daemon port can not be empty.");
                }
                else {
                    match l3q_port_str.parse::<u16>() {
                        Ok(p) => {
                            l3q_port = p;
                            break;
                        },
                        Err(_) => {
                            eprintln!("Failed to parse validate port.");
                            eprintln!("Value must be a positive interger between {} and {}",
                                      u16::MIN, u16::MAX);
                        }
                    }
                }
            },
            Err(e) => {
                eprintln!("Failed to parse stdin.\n{}", e);
            }
        }

        l3q_port_str = String::new();
        eprintln!("Please try again!");
    }

    let conf = match get_config() {
        Ok(c) => c,
        Err((c, e)) => {
            eprintln!("{e}");
            c
        },
    };

    println!("");

    let pg = PasswordGenerator {
        length: 50,
        numbers:true,
        lowercase_letters: true,
        uppercase_letters: true,
        symbols: false,
        spaces: false,
        exclude_similar_characters: false,
        strict: true,
    };

    let self_valid;
    match pg.generate_one() {
        Ok(p) => self_valid = p,
        Err(e) => {
            log::error!("Failed to generate new node validate string: {}", e);
            std::process::exit(8);
        }
    }

    // save new password to database
    let q = DbQuery::InsertValidate {
        ip: String::from("localhost"),
        name: String::from("self"),
        key: self_valid.clone(),
    };

    match db::insert_validate(&conf.db_file, q) {
        Ok(_) => (),
        Err(e) => {
            eprintln!("Failed to insert created validation key into database: {},\n{}",
                      conf.db_file, e);
            std::process::exit(9);
        }
    }

    
    match validate_host(l3q_port, l3q_daemon.clone(), Some(self_valid), hostname, conf.port) {
        Ok(v) => {
            let mut l3qd_host = l3q_daemon.clone();

            match l3q_daemon.parse::<std::net::IpAddr>() {
                Ok(i) => {
                    match lookup_addr(&i) {
                        Ok(h) => l3qd_host = h,
                        Err(_) => (),
                    }
                },
                Err(_) => (),
            }
            
            let q = DbQuery::InsertValidate {
                ip: l3qd_host,
                name: String::from("l3qd"),
                key: v,
            };

            match db::insert_validate(&conf.db_file, q) {
                Ok(_) => {
                    println!("");
                    println!("Restart of local node-l3qd is required!");
                    println!("");
                    println!("Validation SUCCESS!!");
                },
                Err(e) => {
                    eprintln!("Failed to insert validation key from l3qd into database: {}\n{}",
                              conf.db_file, e);
                    std::process::exit(10);
                },
            }

        },
        Err(e) => {
            eprintln!("{e}");
            std::process::exit(5);
        }
    }
}


/// Returns the validation string
/// that is required to communicate
/// with central L3q daemon.
async fn get_l3qd_validate_string(pool: &Pool) -> String {
    let mut validate = String::from("");    
    let q = DbQuery::GetL3qdValidate;
    
    match execute(pool, q).await {
        Ok(res) => {
            if res.len() == 1 {
                match &res[0] {
                    DbResult::Validate{key, ..} => {
                        validate = key.to_string();
                    },
                    _ => (),
                }
            }
        },
        Err(_) => (),
    }

    validate
}

/// Returns the validation string
/// that is required to be sent 
/// from central L3q daemon.
async fn get_node_l3qd_validate_string(pool: &Pool) -> String {
    let mut validate = String::from("");    

    let q = DbQuery::GetNodeL3qdValidate;
    
    match execute(pool, q).await {
        Ok(res) => {
            if res.len() == 1 {
                match &res[0] {
                    DbResult::Validate{key, ..} => {
                        validate = key.to_string();
                    },
                    _ => (),
                }
            }
        },
        Err(_) => (),
    }

    validate
}


/// Is called when central
/// L3q daemon request
/// execution of job.
async fn l3qd_execute_job(conf: &Config, db: &Pool, job: &mut NodeL3qdJob) -> HttpResponse {
    let response: L3qdNodeResponse;
    let validate = get_l3qd_validate_string(db).await;
    
    if job.tasks.len() >= 1 {
        if job.exec_type == NodeL3qdExecType::Sequence {
            match execute_task(job, 0, &conf) {
                Ok(_) => {
                    response = L3qdNodeResponse {
                        data: L3qdNodeData::TaskExecuted {
                            jobid: job.jobid,
                            task: vec!(job.tasks[0].taskid),
                        },
                        validate: validate,
                    };
                },
                Err(e) => {
                    response = L3qdNodeResponse {
                        data: L3qdNodeData::Error { msg: e.clone() },
                        validate: validate,
                    };
                    log::error!("Failed to execute jod: {}, task: {}, {}",
                                job.jobid, job.tasks[0].taskid, e);
                },
            }
        }
        else {
            let mut task_no: Vec<u64> = Vec::new();
            let mut exec_err_msg = String::from("");
            for i in 0..job.tasks.len() {
                match execute_task(job, i, &conf) {
                    Ok(_) => task_no.push(job.tasks[i].taskid),
                    Err(e) => {
                        if exec_err_msg.len() == 0 {
                            exec_err_msg = e;
                        }
                        else {
                            exec_err_msg = format!("{exec_err_msg}\n{e}");
                        }
                    },
                }
            }

            if task_no.len() == job.tasks.len() {
                response = L3qdNodeResponse {
                    data: L3qdNodeData::TaskExecuted {
                        jobid: job.jobid,
                        task: task_no,
                    },
                    validate: validate,
                };
            }
            else {
                let msg = format!("Failed to execute all tasks in parallel. Job: {}, Tasks started: {}, Taskid: {:?}, Tasks failed: {}\nError messages: {}",
                                  job.jobid, task_no.len(), task_no, job.tasks.len() - task_no.len(), exec_err_msg);
                response = L3qdNodeResponse {
                    data: L3qdNodeData::Error { msg: msg.clone() },
                    validate: validate,
                };
                log::error!("{msg}");
            }
        }
    }
    else {
        response = L3qdNodeResponse {
            data: L3qdNodeData::Error {
                msg: format!("Did not found any task to execute for jobid: {}", job.jobid)
            },
            validate: validate,
        };
        log::error!("Did not found any task to execute for jobid: {}", job.jobid);
    }

    HttpResponse::Ok().json(json!(&response))
}


/// Is called when central
/// L3q daemon request cancel of job.
async fn l3qd_cancel_job(conf: &Config, db: &Pool, jobid: u64) -> HttpResponse {
    let response: L3qdNodeResponse;
    let validate = get_l3qd_validate_string(db).await;
    let mut data: L3qdNodeData;
    let mut job_not_found = true;
    let mut cancel_tasks: Vec<TaskStatus> = Vec::new();
    
    match status_all_tasks(&conf, false) {
        Ok(tasks) => {
            for t in tasks {
                if t.jobid == jobid {
                    job_not_found = false;
                    
		    let task_out_res = Command::new("/usr/bin/systemctl")
			.arg("kill")
			.arg(t.unitname.clone())
			.output();

		    let mut status_t: TaskStatus;
		    match status_task(conf, t.unitname.clone(), true) {
			Ok(st) => {
                            status_t = st
                        },
			Err(e) => {
			    status_t = t;
			    log::error!("Failed to get latest status of cancelled task: {}, {}", status_t.unitname, e);
			},
		    }
		    

		    match task_out_res {
			Ok(to) => {
			    if to.status.success() {
                                if status_t.sub_state == "failed" {
				    let res_failed_res = Command::new("/usr/bin/systemctl")
				        .arg("reset-failed")
				        .arg(status_t.unitname.clone())
				        .output();

				    match res_failed_res {
				        Ok(_) => (),
				        Err(e) => {
					    status_t.active_state = String::from("Node-Err");
					    log::error!("Failed to reset-failed systemd service: {}, systemctl reset-failed failed: {}", status_t.unitname, e)
				        }
				    }
                                }
                                else {
				    let res_exit_res = Command::new("/usr/bin/systemctl")
				        .arg("stop")
				        .arg(status_t.unitname.clone())
				        .output();

				    match res_exit_res {
				        Ok(_) => (),
				        Err(e) => {
					    status_t.active_state = String::from("Node-Err");
					    log::error!("Failed to stop systemd service: {}, systemctl stop failed: {}", status_t.unitname, e)
				        }
				    }
                                }
			    }
			    else {
				status_t.active_state = String::from("Node-Err");
							    log::error!("Failed to kill systemd service: {}, systemctl kill failed", status_t.unitname)
			    }
			},
			Err(e) => {
			    status_t.active_state = String::from("Node-Err");
			    log::error!("Failed to kill systemd service: {}, {}", status_t.unitname, e)
			},
		    }

		    cancel_tasks.push(status_t);
                 }
            }

	    data = L3qdNodeData::CancelledTasks {
		tasks: cancel_tasks,
	    };
        },
        Err(e) => {
	    data = L3qdNodeData::Error {
		msg: format!("Failed to get status for all running tasks: {e}"),
	    };
            log::error!("Failed to get status for all running tasks: {e}")
	},
    }

    if job_not_found {
	data = L3qdNodeData::Error {
	    msg: format!("No tasks to cancel found for jobid: {}", jobid),
	};
	log::error!("No tasks to cancel found for jobid: {}", jobid);
    }


    response = L3qdNodeResponse {
	data: data,
	validate: validate,
    };

    HttpResponse::Ok().json(json!(&response))
}


/// Is called when central
/// L3q daemon request status
async fn l3qd_status(sysd_slice: &String, db: &Pool) -> HttpResponse {
    let cpus = num_cpus::get();
    let avail_cpus = match get_available_cpus(sysd_slice) {
        Ok(c) => c.len() as i32,
        Err(e) => {
            log::error!("Failed to get available CPUs, {e}");
            -1
        },
    };

    let mut used_cpus = -1;

    if avail_cpus >= 0 {
        used_cpus = cpus as i32 - avail_cpus;
    }
    
    let mut sys = System::new_all();
    sys.refresh_all();

    let tot_memory = sys.total_memory();
    let used_memory = sys.used_memory();
    let tot_swap = sys.total_swap();
    let used_swap = sys.used_swap();

    let data = L3qdNodeData::NodeStatus {
        total_cpus: cpus,
        used_cpus: used_cpus,
        total_memory: tot_memory,
        used_memory: used_memory,
        total_swap: tot_swap,
        used_swap: used_swap,
    };

    let resp = L3qdNodeResponse {
        data: data,
        validate: get_l3qd_validate_string(db).await,
    };
    
    HttpResponse::Ok().json(json!(&resp))
}

/// Respons to requests from
/// main L3q daemon.
/// If request is wrong default
/// respons will be sent.
#[post("/l3qd/request")]
async fn l3qd_request(conf: web::Data<Config>, db: web::Data<Pool>, cmd: Option<String>) -> HttpResponse {
    let mut response = default_handler().await;

    if let Some(l3qd_cmd) = cmd {
        // test
        println!("Some cmd: {:?}", l3qd_cmd);

        match serde_json::from_str::<L3qdRequest>(&l3qd_cmd) {
            Ok(c) => {
                // test
                println!("Is l3qd request");
                println!("Client validate: {}", c.validate);
                println!("Server validate: {:?}", get_node_l3qd_validate_string(&db).await);
                if c.validate == get_node_l3qd_validate_string(&db).await {
                    // test
                    println!("Validate OK");
                    match c.cmd {
                        L3qdCommand::Status => {
                            response = l3qd_status(&conf.systemd_slice_name, &db).await
                        },
                        L3qdCommand::ExecuteL3qdJob { mut job } => {
                            response = l3qd_execute_job(&conf, &db, &mut job).await
                        },
                        L3qdCommand::CancelL3qdJob { jobid } => {
                            response = l3qd_cancel_job(&conf, &db, jobid).await
                        }
                    }
                }
            },
            Err(_) => (),
        }
    }
    
    response
}


/// Is called when request is not correct.
async fn default_handler() -> HttpResponse {
    HttpResponse::Ok().json(json!({}))
}


/// Starts node-l3qd in daemon
/// mode which is the normal
/// operation mode.
/// Will setup communication
/// with central L3q daemon (l3qd)
/// and exectute tasks received
/// from l3qd.
#[actix_web::main]
async fn start_daemon(conf: Config) -> std::io::Result<()> {
    // Init log
    let mut log_builder = env_logger::Builder::new();
    log_builder.format(| buf, record | {
	writeln!(
	    buf,
	    "{}  {:5} => {}",
	    chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
	    record.level(),
	    record.args()
	)
    });
    log_builder.filter_level(log::LevelFilter::Info);

    match std::fs::OpenOptions::new()
	.append(true)
	.create(true)
	.open(conf.l3qd_log.clone()) {
	    Ok(f) => {
		log_builder.target(env_logger::fmt::Target::Pipe(Box::new(f)));
	    },
	    Err(e) => {
		eprintln!("Failed to open log file: {}", conf.l3qd_log.clone());
		eprintln!("{}", e);
		eprintln!("Printing log to stdout ...");
		log_builder.target(env_logger::fmt::Target::Stdout);
	    }
	}
	
    log_builder.init();

    let task_conf = Config {
        port: conf.port,
        l3qd_port: conf.l3qd_port,
        db_file: conf.db_file.clone(),
        l3qd_log: conf.l3qd_log.clone(),
        systemd_slice_name: conf.systemd_slice_name.clone(),
        check_status_interval: conf.check_status_interval,
        task_output_directory: conf.task_output_directory.clone(),
    };

    let db_conn;
    match db::init_db(conf.db_file.clone()) {
        Ok(c) => db_conn = c,
        Err(e) => return
            Err(io::Error::new(io::ErrorKind::Other, 
                               format!("Failed to initialize database at path: {}\n{}",
                                       conf.db_file, e))),
    }

    //let validate = get_node_l3qd_validate_string(&db_conn).await;

    let mut validate = String::from("");    
    let mut l3qd_host = String::from("");
    
    match execute(&db_conn, DbQuery::GetNodeL3qdValidate).await {
        Ok(res) => {
            if res.len() == 1 {
                match &res[0] {
                    DbResult::Validate{ip, key, ..} => {
                        validate = key.to_string();
                        l3qd_host = ip.to_string();
                    },
                    _ => (),
                }
            }
        },
        Err(_) => (),
    }

    let names = vec!["localhost".to_string(),
                     gethostname().to_string_lossy().to_string()];

    let cert;
    match generate_simple_self_signed(names) {
        Ok(c) => cert = c,
        Err(e) => {
            log::error!("Failed to create certificate: {e}");
            std::process::exit(9);
        },
    }

    let cert_der;
    match cert.serialize_der() {
        Ok(c) => cert_der = c,
        Err(e) => {
            log::error!("Failed to serialize cert as der: {e}");
            std::process::exit(10);
        },
    }

    let x509_der;
    match X509::from_der(&cert_der) {
        Ok(x) => x509_der = x,
        Err(e) => {
            log::error!("Failed to create x509 cert from der: {e}");
            std::process::exit(11);
        },
    }

    let private_key;
    match PKey::private_key_from_der(&cert.serialize_private_key_der()) {
        Ok(p) => private_key = p,
        Err(e) => {
            log::error!("Failed to create private key from der: {e}");
            std::process::exit(12);
        },
    }
    
    let mut ssl_builder;
    match SslAcceptor::mozilla_intermediate(SslMethod::tls()) {
        Ok(b) => ssl_builder = b,
        Err(e) => {
            log::error!("Failed to create SSL Acceptor: {e}");
            std::process::exit(13);
        },        
    }

    match ssl_builder.set_certificate(&x509_der) {
        Ok(_) => (),
        Err(e) => {
            log::error!("Failed to set certificate in ssl builder: {e}");
            std::process::exit(14);
        },        
    }

    match ssl_builder.set_private_key(&private_key) {
        Ok(_) => (),
        Err(e) => {
            log::error!("Failed to set private key in ssl builder: {e}");
            std::process::exit(15);
        },
    }
    
    
    let task_actor = SyncArbiter::start(1, || TaskStatusActor);
    task_actor.do_send(
        TaskStatusActorStart {
            conf: task_conf,
            validate: validate,
            l3qd_host: l3qd_host
        });

    log::info!("Starting node L3q daemon");
    let port = conf.port;
    
    HttpServer::new(move || {
        App::new()
            .wrap(middleware::Logger::default())
            .app_data(web::Data::new(conf.clone()))
            .app_data(web::Data::new(db_conn.clone()))
            .service(l3qd_request)
            .default_service(web::to(default_handler))
    })
        .bind_openssl(format!("0.0.0.0:{port}"), ssl_builder)?
        .workers(1)
        .run()
        .await
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let conf = match get_config() {
        Ok(c) => c,
        Err((c, e)) => {
            eprintln!("{e}");
            c
        },
    };
    
    match args.len() {
        1 => print_tasks(&conf),
        2 => {
            match args[1].as_str() {
                "--help" | "-h" => 
                    print_help(),
                "--version" | "-V" => 
                    print_version(
                        env!("CARGO_PKG_AUTHORS"),
                        env!("CARGO_PKG_NAME"),
                        env!("CARGO_PKG_VERSION")
                    ),
                "--status" => {
                    print_tasks(&conf)
                },
                "--validate" => {
                    validate();
                },
                "--daemon" => {
                    match start_daemon(conf) {
                        Ok(_) => (),
                        Err(e) => {
                            eprintln!("Failed to start node-l3qd daemon: {}", e);
                            std::process::exit(1);
                        },
                    }
                },
                _ => print_wrong_args(),
            }
        },
        _ => {
            let mut help = false;
            for a in args {
                if a.as_str() == "--help" || a.as_str() == "-h" {
                    print_help();
                    help = true;
                    break;
                }
            }

            if ! help {
                print_wrong_args();
            }
        },
    }
}
